package com.example.activiti7.domain.service.impl;

import com.example.activiti7.domain.model.req.QueryProcessInstanceListReq;
import com.example.activiti7.domain.model.req.StartInstanceReq;
import com.example.activiti7.domain.model.resp.Page;
import com.example.activiti7.domain.model.resp.Response;
import com.example.activiti7.domain.model.vo.HistoricProcessInstanceVO;
import com.example.activiti7.domain.model.vo.ProcessInstanceVO;
import com.example.activiti7.domain.service.ProcessInstanceService;
import com.example.activiti7.domain.service.image.CustomProcessDiagramGenerator;
import org.activiti.bpmn.model.*;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricActivityInstanceQuery;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.runtime.ProcessInstanceQuery;
import org.activiti.engine.task.IdentityLink;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class ProcessInstanceServiceImpl implements ProcessInstanceService {

    private final RuntimeService runtimeService;
    private final HistoryService historyService;
    private final CustomProcessDiagramGenerator processDiagramGenerator;
    private final RepositoryService repositoryService;

    public ProcessInstanceServiceImpl(RuntimeService runtimeService,
                                      HistoryService historyService,
                                      RepositoryService repositoryService) {
        this.runtimeService = runtimeService;
        this.historyService = historyService;
        this.processDiagramGenerator = new CustomProcessDiagramGenerator();
        this.repositoryService = repositoryService;
    }


    @Override
    public Response<ProcessInstanceVO> start(StartInstanceReq req) {
        if (StringUtils.isBlank(req.getStartUserId())) {
            return Response.error("startUserId流程启动用户不能为空");
        }
        if (StringUtils.isBlank(req.getProcessDefinitionId())) {
            return Response.error("processDefinitionId不能为空");
        }
        List<IdentityLink> identityLinksForProcessDefinition = repositoryService.getIdentityLinksForProcessDefinition(req.getProcessDefinitionId());
        if (!CollectionUtils.isEmpty(identityLinksForProcessDefinition)
                && identityLinksForProcessDefinition.stream()
                .filter(el -> req.getStartUserId().equalsIgnoreCase(el.getUserId()) || req.getStartUserId().equalsIgnoreCase(el.getGroupId()))
                .count() == 0) {
            return Response.error(req.getStartUserId() + "，用户没有启动流程的权限");
        }

        //设置授权启动人
        Authentication.setAuthenticatedUserId(req.getStartUserId());
        //查询流程定义是否存在
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(req.getProcessDefinitionId())
                .singleResult();

        if (ObjectUtils.isEmpty(processDefinition)) {
            return Response.error("流程定义不存在");
        }

        ProcessInstance processInstance;
        if (StringUtils.isNotBlank(req.getBusinessKey())) {
            processInstance = runtimeService.startProcessInstanceById(processDefinition.getId(), req.getBusinessKey(), req.getVariables());
        } else {
            processInstance = runtimeService.startProcessInstanceById(processDefinition.getId(), req.getVariables());
        }

        if (processInstance != null) {
            ProcessInstanceVO processInstanceVO = new ProcessInstanceVO();
            BeanUtils.copyProperties(processInstance, processInstanceVO);
            return Response.success(processInstanceVO, "启动成功");
        }

        return Response.error("failed");
    }

    @Override
    public void previewDiagram(HttpServletResponse response, String processInstanceId) throws IOException {
        InputStream is = getProcessDiagramInputStream(processInstanceId.replaceAll(".svg", ""));
        if (is == null) {
            return;
        }
        response.setContentType("image/svg+xml");
        // 输出资源内容到相应对象
        byte[] b = new byte[1024];
        int len;
        while ((len = is.read(b, 0, 1024)) != -1) {
            response.getOutputStream().write(b, 0, len);
        }
        response.getOutputStream().flush();
    }

    @Override
    public Response<Boolean> deleteByProcessInstanceID(String processInstanceId) {
        runtimeService.deleteProcessInstance(processInstanceId, "删除" + processInstanceId);
        return Response.success(true, "删除成功");
    }

    @Override
    public Response<List<HistoricProcessInstanceVO>> queryHisByProcessDefinitionId(String processDefinitionId) {
        //流程定义ID
        List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery()
                .processDefinitionId(processDefinitionId)
                .list();

        if (CollectionUtils.isEmpty(list)) {
            Response.success(new ArrayList<>(), "查无数据");
        }

        List<HistoricProcessInstanceVO> result = new ArrayList<>(list.size());
        list.forEach(item -> {
            HistoricProcessInstanceVO historicProcessInstanceVO = new HistoricProcessInstanceVO();
            BeanUtils.copyProperties(item, historicProcessInstanceVO);
            result.add(historicProcessInstanceVO);
        });

        return Response.success(result, "查询成功");
    }

    @Override
    public Response<Page<ProcessInstanceVO, QueryProcessInstanceListReq>> queryProcessInstances(QueryProcessInstanceListReq req) throws Exception {
        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
        if (StringUtils.isNotBlank(req.getName())) {
            processInstanceQuery = processInstanceQuery.processInstanceNameLikeIgnoreCase(req.getName());
        }
        if (StringUtils.isNotBlank(req.getStartUserId())) {
            processInstanceQuery = processInstanceQuery.startedBy(req.getStartUserId());
        }
        if (!CollectionUtils.isEmpty(req.getProcessDefinitionIds())) {
            processInstanceQuery = processInstanceQuery.processDefinitionIds(req.getProcessDefinitionIds());
        }

        List<ProcessInstance> runningList = processInstanceQuery.listPage((req.getPageIndex() - 1) * req.getPageSize(), req.getPageSize());
        if (CollectionUtils.isEmpty(runningList)) {
            return Response.success(Page.builder(new ArrayList<>(), req), "查无数据");
        }

        List<ProcessInstanceVO> result = new ArrayList<>(runningList.size());

        runningList.forEach(processInstance -> {
            ProcessInstanceVO processInstanceVO = new ProcessInstanceVO();
            BeanUtils.copyProperties(processInstance, processInstanceVO);
            //Map<String, Object> var = runtimeService.getVariables(processInstance.getProcessInstanceId());
            //processInstanceVO.setProcessVariables(var);
            result.add(processInstanceVO);
        });

        return Response.success(Page.builder(result, processInstanceQuery.count(), req), "查询成功");
    }

    /**
     * 根据实例ID获取当前实例流程设计图
     *
     * @param processInstanceId
     * @return
     */
    private InputStream getProcessDiagramInputStream(String processInstanceId) {
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());

        HistoricActivityInstanceQuery historyInstanceQuery = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId);

        List<HistoricActivityInstance> historicActivityInstanceAllList = historyInstanceQuery
                .orderByHistoricActivityInstanceStartTime().asc().list();

        HistoricActivityInstance historicActivityInstanceLast = historicActivityInstanceAllList.get(historicActivityInstanceAllList.size() - 1);
        List<HistoricActivityInstance> historicActivityInstanceList = new ArrayList<>(historicActivityInstanceAllList.size());
        for (int i = 0; i < historicActivityInstanceAllList.size(); i++) {
            if (historicActivityInstanceAllList.get(i).getActivityId().equals(historicActivityInstanceLast.getActivityId())) {
                historicActivityInstanceList.add(historicActivityInstanceAllList.get(i));
                break;
            }
            historicActivityInstanceList.add(historicActivityInstanceAllList.get(i));
        }

        InputStream is;
        //流程未执行，则直接显示流程图
        if (CollectionUtils.isEmpty(historicActivityInstanceList)) {
            is = processDiagramGenerator.generateDiagramCustom(bpmnModel, null, null, null, "宋体", "宋体", "宋体");
            return is;
        }
        List<String> highLightedActivityIds = historicActivityInstanceList.stream().map(item -> item.getActivityId()).distinct()
                .collect(Collectors.toList());

        List<String> highLightedFlowIds = getHighLightElementIds(bpmnModel,
                historicActivityInstanceList);

        // 正在执行的节点
        List<String> runningActivityIds = runtimeService.createExecutionQuery().processInstanceId(processInstanceId).list()
                .stream().map(org.activiti.engine.runtime.Execution::getActivityId).filter(Objects::nonNull).collect(Collectors.toList());

        is = processDiagramGenerator.generateDiagramCustom(bpmnModel, highLightedActivityIds, runningActivityIds, highLightedFlowIds, "宋体",
                "宋体", "宋体");
        return is;
    }

    /**
     * 流程图高亮算法
     *
     * @param bpmnModel
     * @param historicActivityInstances
     * @return
     */
    private static List<String> getHighLightElementIds(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
        List<String> elementIds = new ArrayList<>();

        List<HistoricActivityInstance> historicActivityList = new ArrayList<>();
        Collections.sort(historicActivityInstances, (o1, o2) -> o2.getStartTime().compareTo(o1.getStartTime()));
        historicActivityInstances.forEach(item -> {
            if (historicActivityList.stream().filter(x -> x.getId().equals(item.getId())).count() == 0) {
                historicActivityList.add(item);
            }
        });

        // 获取流程图元素节点信息
        Map<String, FlowElement> flowElementMap = bpmnModel.getMainProcess().getFlowElementMap();
        // 找StartEvent
        final FlowNode[] startFlowNodes = new FlowNode[1];
        flowElementMap.forEach((key, value) -> {
            if (value instanceof StartEvent) {
                startFlowNodes[0] = (FlowNode) value;
                return;
            }
        });

        FlowNode startNode = startFlowNodes[0];
        //添加开始
        elementIds.add(startNode.getId());
        startNode.getOutgoingFlows().forEach(item -> {
            buildNextNode(elementIds, flowElementMap.get(item.getId()), flowElementMap, historicActivityList);
        });

        return elementIds;
    }

    /**
     * 线路递归算法
     *
     * @param elementIds
     * @param flowElement
     * @param flowElementMap
     * @param historicActivityList
     */
    private static void buildNextNode(List<String> elementIds, FlowElement flowElement, Map<String, FlowElement> flowElementMap, List<HistoricActivityInstance> historicActivityList) {
        FlowNode flowNode;
        String prevNodeId = "";
        // 如果是连接线直接添加
        if (flowElement instanceof SequenceFlow) {
            elementIds.add(flowElement.getId());
            prevNodeId = flowElement.getId();
            flowNode = (FlowNode) flowElementMap.get(((SequenceFlow) flowElement).getTargetRef());
        } else {
            flowNode = (FlowNode) flowElement;
        }

        // 是否已到达当前节点
        HistoricActivityInstance currentTask = historicActivityList.stream().filter(x -> x.getActivityId().equals(flowNode.getId())).findFirst().orElse(null);
        if (currentTask == null) {
            elementIds.remove(elementIds.size() - 1);
            return;
        }

        SequenceFlow prevSequenceFlow = (SequenceFlow) flowElementMap.get(prevNodeId);
        if (!(flowElementMap.get(prevSequenceFlow.getSourceRef()) instanceof StartEvent)) {
            // 是否已驳回或者没有走通
            HistoricActivityInstance prevTask = historicActivityList.stream().filter(x -> x.getActivityId().equals(prevSequenceFlow.getSourceRef())).findFirst().orElse(null);

            if (prevTask == null) {
                elementIds.remove(elementIds.size() - 1);
                return;
            }

            if (currentTask.getStartTime().compareTo(prevTask.getStartTime()) < 0) {
                elementIds.remove(elementIds.size() - 1);
                return;
            }
        }

        // 当前节点
        elementIds.add(flowNode.getId());

        // 下一个节点
        flowNode.getOutgoingFlows().forEach(item -> {
            buildNextNode(elementIds, flowElementMap.get(item.getId()), flowElementMap, historicActivityList);
        });
    }
}
